/*****************************************************************************/
/* The contents of this file are subject to the Mozilla Public License       */
/* Version 1.1 (the "License"); you may not use this file except in          */
/* compliance with the License.  You may obtain a copy of the License at     */
/* http://www.mozilla.org/MPL/                                               */
/*                                                                           */
/* Software distributed under the License is distributed on an "AS IS"       */
/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the  */
/* License for the specific language governing rights and limitations under  */
/* the License.                                                              */
/*                                                                           */
/* The Original Code is the DSML Tools.                                      */
/*                                                                           */
/* The Initial Developer of the Original Code is Data Connection Limited.    */
/* Portions created by Data Connection are Copyright (C) 2000 Data           */
/* Connection Limited.  All Rights Reserved.                                 */
/*                                                                           */
/* Contributor(s):                                                           */
/*                                                                           */
/* Alternatively, the contents of this file may be used under the terms of   */
/* the GNU General Public License Version 2 or later (the "GPL"), in which   */
/* case the provisions of the GPL are applicable instead of those above.  If */
/* you wish to allow use of your version of this file only under the terms   */
/* of the GPL and not to allow others to use your version of this file under */
/* the MPL, indicate your decision by deleting the provisions above and      */
/* replace them with the notice and other provisions required by the GPL.    */
/* If you do not delete the provisions above, a recipient may use your       */
/* version of this file under either the MPL or the GPL.                     */
/*                                                                           */
/* The Original Code was written to the Glory of God, for Data Connection,   */
/* by Gervase Markham <gerv@dsmltools.org>                                   */
/*****************************************************************************/
package org.dsmltools;

import java.util.*;
//import netscape.ldap.*;
//import netscape.ldap.util.*;
import org.w3c.dom.*;
import com.novell.ldap.LDAPAttributeSchema;
import com.novell.ldap.LDAPSchema;
import com.novell.ldap.LDAPObjectClassSchema;
import com.novell.ldap.LDAPEntry;
import com.novell.ldap.LDAPAttribute;
import com.novell.ldap.LDAPAttributeSet;
import com.novell.ldap.LDAPSchemaElement;

public class DSMLLDAPConverter
{

/** This function converts a Node representation of a directory entry into
    an LDAPEntry one. The format of a Node entry is as follows:
    <PRE>
    *                              |&lt;oc-value&gt;----"top"
    *            |&lt;objectclass>----|&lt;oc-value&gt;----"person"
    *            |                 |&lt;oc-value&gt;----"organization"
    *            |                  ...
    *            |
    * [xiNode]   | (name)   |&lt;value&gt;----"fred@flintstone.com"
    * &lt;entry ----|&lt;attr ----|&lt;value&gt;----"fflintstone@quarry.com"
    *  dn=""&gt;    | name=""&gt; |
    *            |&lt;attr&gt;    |&lt;value&gt;----"fred@somepub.com"
    *            |&lt;attr&gt;     ...
    *             ...
    </PRE>

  @param xiNode The node representing the &lt;entry&gt; tag in the above diagram

  @return An LDAPEntry representing the data in the node passed in
  */
  static LDAPEntry nodeToEntry(Node xiNode)
  {
    /*************************************************************************/
    /* Local variables                                                       */
    /*************************************************************************/
    LDAPAttribute lAttr    = null;         /* an individual LDAPAttribute */
    LDAPAttribute    lOCAttr  = null;       /* the objectclass LDAPAttribute */
    LDAPAttributeSet lAttrSet = new LDAPAttributeSet();
                                           /* a collection of LDAPAttributes */
    String lAttrName  = null;                           /* an attribute name */
    String lAttrValue = null;                /* one of an attribute's values */
    String lDN        = ((Element)xiNode).getAttribute("dn");  /* entry's DN */

    Node   lValueNode = null;                              /* a <value> Node */
    Node   lTextNode  = null;                                 /* a text Node */

    /*************************************************************************/
    /* Iterate through the directory attribute nodes, extracting information */
    /* from each                                                             */
    /*************************************************************************/
    Node lAttrNode = xiNode.getFirstChild();

    while (lAttrNode != null)
    {
      if (lAttrNode.getNodeType() == Node.ELEMENT_NODE)
      {
        /*********************************************************************/
        /* Get the "name" XML attribute (e.g.  cn, sn)                       */
        /*********************************************************************/
        lAttrName = ((Element)lAttrNode).getAttribute("name");

        if (!lAttrName.equals(""))
        {
          /*******************************************************************/
          /* The directory attribute we are looking at has a name - it's a   */
          /* normal directory attribute                                      */
          /*******************************************************************/
          lAttr = nodeToAttr(lAttrNode);

          /*******************************************************************/
          /* Add the LDAPAttribute to the LDAPAttributeSet                   */
          /*******************************************************************/
          lAttrSet.add(lAttr);
        }
        else
        {
          /*******************************************************************/
          /* This must be an "objectclass" child, which doesn't have a       */
          /* "name" attribute.  We want to add all objectclasses to the same */
          /* attribute, hence the protection                                 */
          /*******************************************************************/
          if (lOCAttr == null)
          {
            lOCAttr = new LDAPAttribute("objectclass");
          }

          /*******************************************************************/
          /* Iterate through the values, adding to the LDAPAttribute         */
          /*******************************************************************/
          lValueNode = lAttrNode.getFirstChild();

          while (lValueNode != null &&
                 lValueNode.getNodeType() != Node.COMMENT_NODE)
          {
            lAttrValue = getSubText(lValueNode);
            lOCAttr.addValue(lAttrValue);
            lValueNode = lValueNode.getNextSibling();
          }
        }
      }

      lAttrNode = lAttrNode.getNextSibling();
    }

    /*************************************************************************/
    /* Add the objectclass LDAPAttribute to the LDAPAttributeSet, now we     */
    /* know there are no more to come                                        */
    /*************************************************************************/
    lAttrSet.add(lOCAttr);

    /*************************************************************************/
    /* We now have a full LDAPAttributeSet for this entry. Create the        */
    /* LDAPEntry object from it and the DN                                   */
    /*************************************************************************/
    LDAPEntry lEntry = new LDAPEntry(lDN, lAttrSet);

    return lEntry;

  } /* nodeToEntry() */

/** Converts the tree based at an &lt;attr&gt; Element into an
    LDAPAttribute object.

  @param xiNode The Node to convert

  @return The LDAPAttribute object equivalent to that node
  */
  static private LDAPAttribute nodeToAttr(Node xiNode)
  {
    LDAPAttribute lAttr      = null;          /* the LDAPAttribute to return */
    String        lAttrName  = null;             /* the name of an attribute */
    String        lAttrValue = null;    /* one of the values of an attribute */
    String        lEncoding  = null;          /* how an attribute is encoded */

    NodeList      lValues    = null;
    Node          lValueNode = null;
    Node          lTextNode  = null;
    int           lCount     = 0;
    int           lLength    = 0;

    lAttrName = ((Element)xiNode).getAttribute("name");

    lAttr = new LDAPAttribute(lAttrName);

    lValues = ((Element)xiNode).getElementsByTagName("value");
    lLength = lValues.getLength();

    /*************************************************************************/
    /* Iterate through the values, adding to the LDAPAttribute               */
    /*************************************************************************/
    for (lCount = 0; lCount < lLength; lCount++)
    {
      lValueNode = lValues.item(lCount);
      lTextNode  = lValueNode.getFirstChild();

      /***********************************************************************/
      /* Skip past any comments there might be, to first text node           */
      /***********************************************************************/
      while ((lTextNode != null) &&
             (lTextNode.getNodeType() != Node.TEXT_NODE))
      {
        lTextNode = lTextNode.getNextSibling();
      }

      if (lTextNode == null)
      {
        /*********************************************************************/
        /* <value> tags are empty (is this legal?)                           */
        /*********************************************************************/
        lAttrValue = new String("");
      }
      else
      {
        /*********************************************************************/
        /* We have to decode values encoded with base64; we don't understand */
        /* other encodings.                                                  */
        /*********************************************************************/
        lAttrValue = lTextNode.getNodeValue();

        lEncoding  = ((Element)lValueNode).getAttribute("encoding");

        if (lEncoding.equals(""))
        {
          /*******************************************************************/
          /* No special encoding; add the value as a String                  */
          /*******************************************************************/
          lAttr.addValue(lAttrValue);
        }
        else if (lEncoding.equals("base64"))
        {
          /*******************************************************************/
          /* Base64 encoding                                                 */
          /*******************************************************************/
          /*todo: commons-codec
          ByteBuf lEncoded = new ByteBuf(lAttrValue);
          ByteBuf lDecoded = new ByteBuf();

          MimeBase64Decoder lDecoder = new MimeBase64Decoder();
          lDecoder.translate(lEncoded, lDecoded);

          lAttr.addValue(lDecoded.toBytes());
            */  System.err.println("Unknown encoding \"" + lEncoding + "\". " + "Losing attribute.");
        }
        else
        {
          /*******************************************************************/
          /* Unknown encoding                                                */
          /*******************************************************************/
          System.err.println("Unknown encoding \"" + lEncoding + "\". " +
                             "Losing attribute.");
        }
      }
    }

    return lAttr;

  } /* nodeToAttr() */

  /***************************************************************************/
  /* LDAP to Document conversion methods                                     */
  /***************************************************************************/

/** This function converts an LDAPAttributeSchema into the equivalent
    Document representation.  The representation we are creating is as
    follows:
    <PRE>
    *                        |&lt;name&gt;----"cn"
    *                        |&lt;object-identifier&gt;----"2.5.4.3"
    * &lt;attribute-type -------|   [and, optionally:]
    *  id="", superior=""&gt;   |&lt;description&gt;----"Common name"
    *                         ...
    </PRE>

  @param xiAttrSchema The LDAPAttributeSchema to convert
  @param xiOwnerDoc The owner of the new node (nodes must have an owner)

  @return The attribute-type Element at the root of the tree
  @return this attribute type in a state such that it has not been
  @return into the tree anywhere.
  */
  static Element attrTypeToNode(LDAPAttributeSchema xiAttrSchema,
                                Document            xiOwnerDoc)
  {
    /*************************************************************************/
    /* Local variables                                                       */
    /*************************************************************************/
    Element  lElem     = null;                                 /* an Element */
    Text     lValElem  = null;                                /* a Text Node */

    Element  lAttrTypeElem = xiOwnerDoc.createElement("attribute-type");
                                               /* an <attribute-type Element */
    String   lSuperior = null;         /* superior XML attr of lAttrTypeElem */
    String[] lTempArray = null;

    /*************************************************************************/
    /* Set the attributes on the attribute-type XML element                  */
    /*************************************************************************/
    lAttrTypeElem.setAttribute("id", xiAttrSchema.getName());

    lSuperior = xiAttrSchema.getSuperior();

    if (lSuperior != null)
    {
      lAttrTypeElem.setAttribute("superior", "#" + lSuperior);
    }

    if (xiAttrSchema.isObsolete())
    {
      lAttrTypeElem.setAttribute("obsolete", "true");
    }

    if (xiAttrSchema.isSingleValued())
    {
      lAttrTypeElem.setAttribute("single-value", "true");
    }

    lTempArray = xiAttrSchema.getQualifier(
                                     "NO-USER-MODIFICATION"); //todo: use ietf constant
    if (lTempArray != null)
    {
      lAttrTypeElem.setAttribute("user-modification", "false");
    }


    /*************************************************************************/
    /* NAME                                                                  */
    /*************************************************************************/
    lElem    = xiOwnerDoc.createElement("name");
    lValElem = xiOwnerDoc.createTextNode(xiAttrSchema.getName());
    lElem.appendChild(lValElem);
    lAttrTypeElem.appendChild(lElem);

    /*************************************************************************/
    /* DESC (this one's optional); the default value is (if you read         */
    /* Netscape's code) "".                                                  */
    /*************************************************************************/
    String lDesc = xiAttrSchema.getDescription();

    if (!lDesc.equals(""))
    {
      lElem    = xiOwnerDoc.createElement("description");
      lValElem = xiOwnerDoc.createTextNode(lDesc);
      lElem.appendChild(lValElem);
      lAttrTypeElem.appendChild(lElem);
    }

    /*************************************************************************/
    /* OID                                                                   */
    /*************************************************************************/
    lElem    = xiOwnerDoc.createElement("object-identifier");
    lValElem = xiOwnerDoc.createTextNode(xiAttrSchema.getID());
    lElem.appendChild(lValElem);
    lAttrTypeElem.appendChild(lElem);

    /*************************************************************************/
    /* SYNTAX                                                                */
    /* [ISSUE] Syntax has an XML attribute "bound", the value for which I    */
    /* can't find in the LDAPAttributeSchema.                                */
    /*************************************************************************/
    lElem    = xiOwnerDoc.createElement("syntax");
    lValElem = xiOwnerDoc.createTextNode(xiAttrSchema.getSyntaxString());
    lElem.appendChild(lValElem);
    lAttrTypeElem.appendChild(lElem);

    int lSize  = 0;
    int lCount = 0;

    /*************************************************************************/
    /* ORDERING                                                              */
    /*************************************************************************/
    //lTempArray = xiAttrSchema.getQualifier(LDAPAttributeSchema.ORDERING);
    lTempArray = xiAttrSchema.getQualifier("ORDERING"); // todo: use itef constants

    if (lTempArray != null)
    {
      lSize = lTempArray.length;

      for (lCount = 0; lCount < lSize; lCount++)
      {
        lElem    = xiOwnerDoc.createElement("ordering");
        lValElem = xiOwnerDoc.createTextNode(lTempArray[lCount]);
        lElem.appendChild(lValElem);
        lAttrTypeElem.appendChild(lElem);
      }
    }

    /*************************************************************************/
    /* EQUALITY                                                              */
    /*************************************************************************/
    //lTempArray = xiAttrSchema.getQualifier(LDAPAttributeSchema.EQUALITY);
    lTempArray = xiAttrSchema.getQualifier("EQUALITY"); // todo: use itef constants

    if (lTempArray != null)
    {
      lSize = lTempArray.length;

      for (lCount = 0; lCount < lSize; lCount++)
      {
        lElem    = xiOwnerDoc.createElement("equality");
        lValElem = xiOwnerDoc.createTextNode(lTempArray[lCount]);
        lElem.appendChild(lValElem);
        lAttrTypeElem.appendChild(lElem);
      }
    }

    /*************************************************************************/
    /* SUBSTRING                                                             */
    /*************************************************************************/
    //lTempArray = xiAttrSchema.getQualifier(LDAPAttributeSchema.SUBSTR);
    lTempArray = xiAttrSchema.getQualifier("SUBSTR"); // todo: use itef constants

    if (lTempArray != null)
    {
      lSize = lTempArray.length;

      for (lCount = 0; lCount < lSize; lCount++)
      {
        lElem    = xiOwnerDoc.createElement("substring");
        lValElem = xiOwnerDoc.createTextNode(lTempArray[lCount]);
        lElem.appendChild(lValElem);
        lAttrTypeElem.appendChild(lElem);
      }
    }

    return lAttrTypeElem;

  } /* attrTypeToNode() */

/** This function converts an LDAPObjectClassSchema into the equivalent
    Document representation. The representation we are creating is as
    follows:
    <PRE>
    *               |&lt;name&gt;----"person"
    * &lt;class -------|&lt;description&gt;----"A person"
    *  id="",       |&lt;object-identifier&gt;----"2.5.6.6"
    *  superior="", |&lt;attribute ----
    *  type=""&gt;       ref="",
    *                 required=""/&gt;
    </PRE>

  @param xiOCSchema The LDAPObjectClassSchema to convert
  @param xiOwnerDoc The owner of the new node (nodes must have an owner)

  @return The class Element at the root of the tree representing this
  the tree anywhere.
  */
  static Element objectclassToNode(LDAPObjectClassSchema xiOCSchema,
                                   Document              xiOwnerDoc)
  {
    /*************************************************************************/
    /* Local variables                                                       */
    /*************************************************************************/
    Element     lElem     = null;                              /* an Element */
    Text        lValElem  = null;                             /* a Text Node */
    String      lSuperior = null;       /* superior XML attribute of <class> */
    //String      lRef      = null;    /* ref XML attribute of <attribute> tag */
    //Enumeration lEnum     = null;
    String[] lEnum     = null;

    Element lClassElem = xiOwnerDoc.createElement("class");

    /*************************************************************************/
    /* Set the attributes on the class XML element                           */
    /*************************************************************************/
    lClassElem.setAttribute("id", xiOCSchema.getName());

    /*************************************************************************/
    /* (superior)                                                            */
    /*************************************************************************/
    //lSuperior = xiOCSchema.getSuperior();
    lSuperior = xiOCSchema.getSuperiors()[0];//todo: handle multiple

    if (lSuperior != null)
    {
      lClassElem.setAttribute("superior", "#" + lSuperior);
    }

    /*************************************************************************/
    /* (type)                                                                */
    /*************************************************************************/
    switch (xiOCSchema.getType())
    {
      case LDAPObjectClassSchema.STRUCTURAL:
        lClassElem.setAttribute("type", "structural");
        break;

      case LDAPObjectClassSchema.ABSTRACT:
        lClassElem.setAttribute("type", "abstract");
        break;

      case LDAPObjectClassSchema.AUXILIARY:
        lClassElem.setAttribute("type", "auxiliary");
        break;

      default:
        break;
    }

    /*************************************************************************/
    /* (obsolete)                                                            */
    /*************************************************************************/
    if (xiOCSchema.isObsolete())
    {
      lClassElem.setAttribute("obsolete", "true");
    }

    /*************************************************************************/
    /* NAME                                                                  */
    /*************************************************************************/
    lElem = xiOwnerDoc.createElement("name");
    lValElem = xiOwnerDoc.createTextNode(xiOCSchema.getName());
    lElem.appendChild(lValElem);
    lClassElem.appendChild(lElem);

    /*************************************************************************/
    /* DESC (this one's optional)                                            */
    /*************************************************************************/
    String lDesc = xiOCSchema.getDescription();

    if (!lDesc.equals(""))
    {
      lElem = xiOwnerDoc.createElement("description");
      lValElem = xiOwnerDoc.createTextNode(lDesc);
      lElem.appendChild(lValElem);
      lClassElem.appendChild(lElem);
    }

    /*************************************************************************/
    /* OID                                                                   */
    /*************************************************************************/
    lElem    = xiOwnerDoc.createElement("object-identifier");
    lValElem = xiOwnerDoc.createTextNode(xiOCSchema.getID());
    lElem.appendChild(lValElem);
    lClassElem.appendChild(lElem);

    /*************************************************************************/
    /* Required attributes                                                   */
    /*************************************************************************/
    lEnum = xiOCSchema.getRequiredAttributes();
    //while (lEnum.hasMoreElements())
    for ( String lRef : lEnum)
    {
      //lRef  = "#" + (String)lEnum.nextElement();
      lRef  = "#" + lRef;
      lElem = xiOwnerDoc.createElement("attribute");
      lElem.setAttribute("ref", lRef);
      lElem.setAttribute("required", "true");
      lClassElem.appendChild(lElem);
    }

    /*************************************************************************/
    /* Optional attributes                                                   */
    /*************************************************************************/
    lEnum = xiOCSchema.getOptionalAttributes();
    //while (lEnum.hasMoreElements())
    for ( String lRef : lEnum)
    {
      //lRef  = "#" + (String)lEnum.nextElement();
      lRef  = "#" + lRef;
      lElem = xiOwnerDoc.createElement("attribute");
      lElem.setAttribute("ref", lRef);
      lElem.setAttribute("required", "false");
      lClassElem.appendChild(lElem);
    }

    return lClassElem;

  } /* objectclassToNode() */

/** This function converts an LDAPEntry into the equivalent Document
    representation. The representation we are creating is as follows:
    <PRE>
    *                             |&lt;oc-value&gt;----"top"
    *            |&lt;objectclass&gt;---|&lt;oc-value&gt;----"person"
    * &lt;entry ----|                 ...
    *  dn=""&gt;    |
    *            |&lt;attr -----|&lt;value&gt;----"peterr@home.com"
    *              name=""&gt;  |&lt;value&gt;----"peter@dsml.org"
    *                         ...
    </PRE>
    We need the schema to check whether a given attribute has String or
    binary data.

  @param xiEntry The LDAPEntry to convert
  @param xiOwnerDoc The owner of the new node (nodes must have an owner)
  @param xiSchema The schema relating to these entries

  @return The entry Element at the root of the tree representing this
    entry, in a state such that it has not been inserted into
    the tree anywhere.
  */
  static Element entryToNode(LDAPEntry  xiEntry,
                             Document   xiOwnerDoc)
  {
    /*************************************************************************/
    /* Local variables                                                       */
    /*************************************************************************/
    Element       lEntryElem = null;        /* the <entry> Element we return */

    Element       lAttrElem  = null;                    /* an <attr> Element */
    //Enumeration   lAttrs     = null;          /* This entry's LDAPAttributes */
    Set   lAttrs     = null;
    LDAPAttribute lAttr      = null;

    String        lName      = null;             /* the name of an attribute */

    Element       lValueElem = null;                /* an <oc-value> Element */
    Enumeration   lValues    = null;      /* the objectclasses of this entry */
    String        lValue     = null;      /* an individual objectclass value */

    //LDAPAttributeSchema lAttrDef = null;      /* the definition of an <attr> */

    /*************************************************************************/
    /* Create the <entry> node (the root of the tree we are making)          */
    /*************************************************************************/
    lEntryElem = xiOwnerDoc.createElement("entry");
    lEntryElem.setAttribute("dn", xiEntry.getDN());

    /*************************************************************************/
    /* First we handle the objectclass                                       */
    /*************************************************************************/
    lAttr = xiEntry.getAttribute("objectclass");
    if (lAttr != null)
    {
      lAttrElem = xiOwnerDoc.createElement("objectclass");

      /***********************************************************************/
      /* Iterate through the objectclasses values, converting to Document    */
      /* representation.                                                     */
      /***********************************************************************/
      lValues = lAttr.getStringValues();

      while (lValues.hasMoreElements())
      {
        lValue = (String)lValues.nextElement();
        lValueElem = xiOwnerDoc.createElement("oc-value");
        lValueElem.appendChild(xiOwnerDoc.createTextNode(lValue));
        lAttrElem.appendChild(lValueElem);
      }

      if (!(lValue == null || lValue.equals("")))
      {
        lEntryElem.appendChild(lAttrElem);
      }
    }

    /*************************************************************************/
    /* Iterate through the attributes, converting to Document                */
    /* representation.                                                       */
    /*************************************************************************/
    //lAttrs = (xiEntry.getAttributeSet()).getAttributes();
    lAttrs = xiEntry.getAttributeSet();

    //while (lAttrs.hasMoreElements())
    for (Object attr : lAttrs)
    {
      //lAttr = (LDAPAttribute)lAttrs.nextElement();
      lAttr = (LDAPAttribute) attr;
      lName = lAttr.getName();

      /***********************************************************************/
      /* The objectclasses have already been output above                    */
      /***********************************************************************/
      if (!(lName.equalsIgnoreCase("objectclass")))
      {
        /*********************************************************************/
        /* We've found a normal directory attribute; create the <attr> node  */
        /* and add the name XML attribute                                    */
        /*********************************************************************/
        lAttrElem = xiOwnerDoc.createElement("attr");
        lAttrElem.setAttribute("name", lName);

        /*********************************************************************/
        /* Iterate through this attribute's values, converting to Document   */
        /* representation.                                                   */
        /*********************************************************************/
        lValues = lAttr.getStringValues();

        while (lValues.hasMoreElements())
        {
          lValue = (String)lValues.nextElement();

          /*******************************************************************/
          /* We have to check the attribute definition to see if this        */
          /* attribute has text or binary data.                              */
          /*******************************************************************/
          lValueElem = xiOwnerDoc.createElement("value");
          //lAttrDef   = xiSchema.getAttribute(lName);

         // if ((lAttrDef != null) &&
        //      (lAttrDef.getSyntax() == LDAPSchemaElement.binary))   // todo: handle base64
        //  {
            /*****************************************************************/
            /* It's binary data - so we base64 encode it                     */
            /*****************************************************************/
        //    MimeBase64Encoder lEncoder = new MimeBase64Encoder();
       //     ByteBuf lDecoded = new ByteBuf(lValue);
       //     ByteBuf lEncoded = new ByteBuf();
       //     lEncoder.translate(lDecoded, lEncoded);
       //     lValue = lEncoded.toString();

            /*****************************************************************/
            /* Mark the <value> as base64                                    */
            /*****************************************************************/
       //     lValueElem.setAttribute("encoding", "base64");
       //   }
       //   else
       //   {
            /*****************************************************************/
            /* It's a string                                                 */
            /*****************************************************************/
       //   }

          /*******************************************************************/
          /* Attach the <value> Element to the <attr> Element                */
          /*******************************************************************/
          lValueElem.appendChild(xiOwnerDoc.createTextNode(lValue));
          lAttrElem.appendChild(lValueElem);
        }
      }

      /***********************************************************************/
      /* Attach the <attr> Element to the <entry> Element, as long as the    */
      /* lValue is not "" (this causes problems)                             */
      /* [ISSUE] <value></value> - need to investigate further               */
      /***********************************************************************/
      if (!(lValue == null || lValue.equals("")))
      {
        lEntryElem.appendChild(lAttrElem);
      }
    }

    return lEntryElem;

  } /* entryToNode() */

/** This method gets the value of the first sub node of the passed in node
    that is a Text node.  This is a common occurrence in XML, and looks
    like: <CODE>&lt;wibble&gt;Fred&lt;/wibble&gt;</CODE>.  Given the
    <CODE>&lt;wibble&gt;</CODE> Node, this function returns the string
    "Fred".

  @param xiNode The node to search below

  @return A String representing the value, or "" if none is found (i.e.
  @return XML is &lt;tag&gt;&lt;/tag&gt;).
  */
  static String getSubText(Node xiNode)
  {
    String lRetVal = new String("");

    /*************************************************************************/
    /* Find the first text node below that passed in                         */
    /*************************************************************************/
    Node lChild = xiNode.getFirstChild();

    while (lChild != null &&
           lChild.getNodeType() != Node.TEXT_NODE)
    {
      lChild = lChild.getNextSibling();
    }

    /*************************************************************************/
    /* If we have found a text node, get the value                           */
    /*************************************************************************/
    if (lChild != null)
    {
      lRetVal = lChild.getNodeValue();
    }

    return lRetVal;

  } /* getSubText() */

} /* DSMLLDAPConverter */